package com.gmrz.uaf.policy.verification;

import com.gmrz.uaf.common.UAFRuntimeException;
import com.gmrz.uaf.protocol.v1.schema.Authenticator;
import com.gmrz.uaf.protocol.v1.schema.MatchCriteria;
import com.gmrz.uaf.protocol.v1.schema.Policy;
import com.google.common.collect.Sets;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.*;

public class StepupPolicyProcessor {
	private static final Logger LOG = LogManager.getLogger(StepupPolicyProcessor.class);

	public static void filterPolicy(Policy policy, Set<Authenticator> registeredList) throws PolicyVerificationException {
		if (policy == null) {
			throwUAFRuntimeException("Policy is null");
		}

		if ((registeredList == null) || (registeredList.isEmpty())) {
			throwUAFRuntimeException("Registered authenticator list is empty.");
		}
        // 查询“已经注册了的认证数据” 中符合策略中无效的认证器说明的  认证数据。
		Set<Authenticator> disallowedAuthenticators = getDisallowedAuthenticators(
				policy.getDisallowed(), registeredList);

		Set registeredAuthenticators = new HashSet(registeredList);

        // 获取两个集合的差集，registeredAuthenticators中有，而disallowedAuthenticators没有的
		Set registeredAllowedAuthenticators = Sets.difference(
				registeredAuthenticators, disallowedAuthenticators);

        // 查询符合策略中有效的认证器的“并且排除了符合无效的认证器的 已经注册了的认证数据”。
		List acceptedCombinations = getAcceptedCombinations(
				policy.getAccepted(), registeredAllowedAuthenticators);

		if(disallowedAuthenticators.size()>0)
		policy.setDisallowed(convertAuthenticatorsToMCs(disallowedAuthenticators));
		//if(acceptedCombinations.size()>0)
		// 将最终符合有效认证器的数据放入 “策略中的允许的认证器说明中”
		policy.setAccepted(convertCombinationsToMCsList(acceptedCombinations));
	}

	private static List<List<MatchCriteria>> convertCombinationsToMCsList(
			List<Set<Authenticator>> acceptedCombinations) {
		List mcsList = new ArrayList();
		if ((acceptedCombinations != null) && (!acceptedCombinations.isEmpty())) {
			for (Set combination : acceptedCombinations) {
				mcsList.add(convertAuthenticatorsToMCs(combination));
			}
		}

		return mcsList;
	}

	public static List<MatchCriteria> convertAuthenticatorsToMCs(
			Set<Authenticator> authenticators) {

		LOG.info("convertAuthenticatorsToMCs begin");
		List<MatchCriteria> mcList = new ArrayList<MatchCriteria>();
		if ((authenticators != null) && (!authenticators.isEmpty())) {
			for (Authenticator a : authenticators) {
				MatchCriteria mc = new MatchCriteria();
				mc.setAAID(Arrays.asList(a.getAAID()));
				mc.setKeyIDList(Arrays.asList(a.getKeyID()));
//				if(null != a.getCertificateBean()) {
//					List<Extension> extensions = mc.getExtensions();
//					if(null == extensions){
//						extensions = new ArrayList<Extension>();
//					}
//					Extension extension = new Extension();
//					extension.setId("certNo");
//					extension.setData(Convert.toBase64(Convert.intToByteArray(a.getCertificateBean().getRegCounter())));
//					extensions.add(extension);
//					mc.setExtensions(extensions);
//				}
				mcList.add(mc);
			}
		}
		LOG.info("convertAuthenticatorsToMCs end");
		return mcList;
	}

	private static List<Set<Authenticator>> getAcceptedCombinations(
			List<List<MatchCriteria>> policyAcceptedMCs,
			Set<Authenticator> registeredAllowedAuthenticators)
			throws PolicyVerificationException {
		List<Set<Authenticator>> acceptedCombinations = new ArrayList<Set<Authenticator>>();

		if (policyAcceptedMCs == null) {
			return acceptedCombinations;
		}
		boolean matched = false;

		for (List<MatchCriteria> policyAcceptedMCList : policyAcceptedMCs) {
			List combinationLists = new ArrayList();
			matched = true;

			for (MatchCriteria policyMC : policyAcceptedMCList) {
				Set<Authenticator> combinationCandidates = new HashSet<Authenticator>();

				for (Authenticator a : registeredAllowedAuthenticators) {
					if (Matcher.compare(policyMC, a.getMetadata())) {
						combinationCandidates.add(a);
					}
				}

				if (combinationCandidates.isEmpty()) {
					matched = false;
					break;
				}

				combinationLists.add(new ArrayList(combinationCandidates));
			}

			if (matched) {
				addCombinations(combinationLists, acceptedCombinations, 0, null);
			}
		}

		return acceptedCombinations;
	}

	private static boolean isCombinationAlreadyPresent(
			List<Set<Authenticator>> combinations,
			Set<Authenticator> newCombination) {
		boolean matched = false;
		for (Set combination : combinations) {
			matched = false;
			if (combination.size() == newCombination.size()) {
				matched = true;
				for (Authenticator a : newCombination) {
					if (!combination.contains(a)) {
						matched = false;
						break;
					}
				}
			}
			if (matched) {
				return true;
			}
		}
		return matched;
	}

	private static void addCombinations(
			List<List<Authenticator>> combinationLists,
			List<Set<Authenticator>> result, int depth,
			Set<Authenticator> combination) {
		if (depth == combinationLists.size()) {
			if ((combination == null)
					|| (combination.size() < combinationLists.size())
					|| (isCombinationAlreadyPresent(result, combination))) {
				return;
			}
			result.add(combination);
			return;
		}

		for (int i = 0; i < combinationLists.get(depth).size(); i++) {
			Set newCombination = new HashSet();
			if (combination != null) {
				newCombination.addAll(combination);
			}
			newCombination.add(combinationLists.get(depth).get(i));
			addCombinations(combinationLists, result, depth + 1, newCombination);
		}
	}

	private static Set<Authenticator> getDisallowedAuthenticators(List<MatchCriteria> disallowedMCs, 
			Set<Authenticator> registeredList) throws PolicyVerificationException {
     Set<Authenticator> disallowed = new HashSet<Authenticator>();
     if (disallowedMCs != null){
       for (MatchCriteria policyMC : disallowedMCs) { 
 
         for (Authenticator a : registeredList)
           if (Matcher.compare(policyMC, a.getMetadata()))
             disallowed.add(a);
       }
     }
     return disallowed;
   }

	private static void throwUAFRuntimeException(String errorMsg) {
		LOG.error(errorMsg);
		throw new UAFRuntimeException(errorMsg);
	}
}
